#include "DbMySql.h"
#include <stdlib.h>

CDbMySql::CDbMySql(void)
{
	m_Mysql = NULL;
	m_pResult = NULL;
	m_ppRecord = NULL;
	m_stop = false;
}

CDbMySql::~CDbMySql(void)
{
	FreeQueryResult();
	Close();
}

void CDbMySql::KeepAlive()
{
	while (!m_stop)
	{
		m_lock.lock();
		if (NULL == m_Mysql) {
			m_lock.unlock();
			std::this_thread::sleep_for(std::chrono::seconds(1));
			continue;
		}

		const char* sql = "select version()";
		int ret = ::mysql_ping(m_Mysql);
		if (RET_SUCCESS != ret)
		{
			m_lock.unlock();
			std::this_thread::sleep_for(std::chrono::seconds(1));
			continue;
		}

		ret = ::mysql_real_query(m_Mysql, sql, strlen(sql));
		if (ret != RET_SUCCESS)
		{
			// No database selected
			ret = ::mysql_errno(m_Mysql);
			if (1046 == ret)
			{
				ret = ::mysql_select_db(m_Mysql, m_szDbName.c_str());
				if (RET_SUCCESS == ret) {
					ret = ::mysql_query(m_Mysql, sql);
				}
			}
		}

		MYSQL_RES* pResult = ::mysql_store_result(m_Mysql);
		if (NULL != pResult)
		{
			MYSQL_ROW pRecord = ::mysql_fetch_row(pResult);
			while (pRecord != NULL)
			{
				pRecord = ::mysql_fetch_row(pResult);
			}
		}
		if (pResult != NULL)
		{
			::mysql_free_result(pResult);
			pResult = NULL;
		}

		m_lock.unlock();

		if (RET_SUCCESS != ret)
		{
			printf("heart beat with database[%s] failed:%d", m_szDbIp.c_str(), m_usDbPort);
		}

		// 分时睡眠-防止死锁
		for (int index = 0; index < 60 * 10 && !m_stop; ++index) {
			std::this_thread::sleep_for(std::chrono::seconds(1));
		}
	}
}

bool CDbMySql::Open(const char* ip, int port, const char* user, const char* pwd, const char* name)
{
	FreeQueryResult();
	Close();

	std::unique_lock<std::mutex> guard(m_lock);
	m_szDbIp = ip;
	m_szDbUser = user;
	m_szDbPwd = pwd;
	m_szDbName = name;
	m_usDbPort = port;

	// 初始化数据库
	if (NULL == (m_Mysql = ::mysql_init(NULL)))
	{
		return false;
	}

	// 打开重连
	char cValue = 1;
	::mysql_options(m_Mysql, MYSQL_OPT_RECONNECT, &cValue);

	// 设置编码
	::mysql_options(m_Mysql, MYSQL_SET_CHARSET_NAME, "UTF8");

	// 连接数据库
	if (NULL == ::mysql_real_connect(m_Mysql, m_szDbIp.c_str(), m_szDbUser.c_str(), m_szDbPwd.c_str(), /*m_szDbName*/"", m_usDbPort, NULL, 0))
	{
		const char* msg = ::mysql_error(m_Mysql);
		printf("connect mysql error: %s", msg);
		return false;
	}

	// 创建数据库
	char szSql[1024] = { 0 };
	sprintf(szSql, "create database if not exists %s character set utf8 collate utf8_general_ci", m_szDbName.c_str());
	_Exec(szSql);

	// 选择数据库
	if (::mysql_select_db(m_Mysql, m_szDbName.c_str()) != RET_SUCCESS)
	{
		return false;
	}

	// 开启mysql连接保活定时器
	m_stop = false;
	m_thread = std::thread(std::bind(&CDbMySql::KeepAlive, this));

	return true;
}

void CDbMySql::Close(void)
{
	std::unique_lock<std::mutex> guard(m_lock);
	if (m_Mysql != NULL)
	{
		m_stop = true;
		if (m_thread.joinable())
		{
			m_thread.join();
		}
		::mysql_close(m_Mysql);
		m_Mysql = NULL;
	}
}

bool CDbMySql::IsTableExist(const char* tbname)
{
	if (NULL == m_Mysql)
		return false;

	char szSql[1024] = { 0 };
	sprintf(szSql, "select * from information_schema.tables where table_schema='%s' and table_name='%s'", m_szDbName.c_str(), tbname);

	bool exist = false;
	if (Query(szSql)) {
		exist = m_ullRow > 0;
		FreeQueryResult();
		return exist;
	}
	return false;
}

bool CDbMySql::CreateTable(const char* sql)
{
	std::unique_lock<std::mutex> guard(m_lock);
	return _Exec(sql);
}

bool CDbMySql::IsProcedureExist(const char* dbname, const char* procname)
{
	if (NULL == m_Mysql)
		return false;

	char szSql[1024] = { 0 };
	sprintf(szSql, "select name from mysql.proc where db = '%s' and type = 'PROCEDURE' and name = '%s'", dbname, procname);

	if (Query(szSql)) {
		FreeQueryResult();
		return true;
	}
	return false;
}

bool CDbMySql::CreateProcedure(const char* sql)
{
	std::unique_lock<std::mutex> guard(m_lock);
	return _Exec(sql);
}

// 注意：有返回结果集的sql语句必须调Query(sql)，而不要调Exec(sql)
// 因为如果mysql_real_query()有返回结果集的话就一定要调用mysql_store_result()和mysql_free_result()处理结果集
// 要不然后面的任意sql语句调用都将失败(2014 Commands out of sync; you can't run this command now)
// 例如："repair table table_name"此sql调用会产生一行或多行结果：table_name repair status OK
//       所以千万不能用Exec("repair table table_name")，而要用Query("repair table table_name")
bool CDbMySql::Exec(const char* sql)
{
	std::unique_lock<std::mutex> guard(m_lock);
	return _Exec(sql);
}

bool CDbMySql::_Exec(const char* sql)
{
	if (NULL == m_Mysql)
		return false;

	int ret = ::mysql_ping(m_Mysql);
	if (RET_SUCCESS != ret)
	{
		ret = mysql_errno(m_Mysql);
		const char* msg = mysql_error(m_Mysql);
		printf("%s", msg);
		return false;
	}

	ret = ::mysql_real_query(m_Mysql, sql, strlen(sql));
	if (ret != RET_SUCCESS)
	{
		// No database selected
		ret = ::mysql_errno(m_Mysql);
		if (1046 == ret)
		{
			ret = ::mysql_select_db(m_Mysql, m_szDbName.c_str());
			if (RET_SUCCESS == ret) {
				ret = ::mysql_query(m_Mysql, sql);
			}
		}
	}

	return (RET_SUCCESS == ret);
}

uint64_t CDbMySql::Insert(const char* sql)
{
	std::unique_lock<std::mutex> guard(m_lock);
	uint64_t ret = 0xffffffff;
	if (_Exec(sql))
		ret = ::mysql_insert_id(m_Mysql);
	return ret;
}

const char* CDbMySql::GetErrorMessage()
{
	return ::mysql_error(m_Mysql);
}

bool CDbMySql::Delete(const char* sql)
{
	std::unique_lock<std::mutex> guard(m_lock);
	return _Exec(sql);
}

bool CDbMySql::Update(const char* sql)
{
	std::unique_lock<std::mutex> guard(m_lock);
	return _Exec(sql);
}

bool CDbMySql::Query(const char* sql)
{
	FreeQueryResult();

	std::unique_lock<std::mutex> guard(m_lock);
	if (NULL == m_Mysql)
		return false;

	if (!this->_Exec(sql))
		return false;

	m_pResult = ::mysql_store_result(m_Mysql);
	if (NULL == m_pResult)
		return false;

	m_ullRow = m_pResult->row_count;
	m_nCol = mysql_num_fields(m_pResult);

	return true;
}

void CDbMySql::FreeQueryResult(void)
{
	std::unique_lock<std::mutex> guard(m_lock);
	if (m_pResult != NULL)
	{
		::mysql_free_result(m_pResult);
		m_pResult = NULL;
	}
	m_ullRow = 0;
	m_nCol = 0;
}

bool CDbMySql::FetchNextRow(void)
{
	std::unique_lock<std::mutex> guard(m_lock);
	if (m_pResult != NULL)
	{
		return ((m_ppRecord = ::mysql_fetch_row(m_pResult)) != NULL);
	}
	return false;
}

long CDbMySql::GetRowCount()
{
	return m_ullRow;
}

int CDbMySql::GetColCount()
{
	return m_nCol;
}

char* CDbMySql::GetColValue(int nCol)
{
	std::unique_lock<std::mutex> guard(m_lock);
	if (NULL == m_ppRecord)
		return NULL;

	return m_ppRecord[nCol];
}